/* *
 *
 *  Copyright (c) 2019-2019 Highsoft AS
 *
 *  Boost module: stripped-down renderer for higher performance
 *
 *  License: highcharts.com/license
 *
 *  !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
 *
 * */
"use strict";
import H from "../../parts/Globals.js";
import U from "../../parts/Utilities.js";
var clamp = U.clamp;
var pick = H.pick;
/* eslint-disable valid-jsdoc */
/**
 * A static shader mimicing axis translation functions found in parts/Axis
 *
 * @private
 * @function GLShader
 *
 * @param {WebGLContext} gl
 *        the context in which the shader is active
 *
 * @return {*}
 */
function GLShader(gl) {
  var vertShade = [
      /* eslint-disable max-len, @typescript-eslint/indent */
      "#version 100",
      "#define LN10 2.302585092994046",
      "precision highp float;",
      "attribute vec4 aVertexPosition;",
      "attribute vec4 aColor;",
      "varying highp vec2 position;",
      "varying highp vec4 vColor;",
      "uniform mat4 uPMatrix;",
      "uniform float pSize;",
      "uniform float translatedThreshold;",
      "uniform bool hasThreshold;",
      "uniform bool skipTranslation;",
      "uniform float xAxisTrans;",
      "uniform float xAxisMin;",
      "uniform float xAxisMinPad;",
      "uniform float xAxisPointRange;",
      "uniform float xAxisLen;",
      "uniform bool  xAxisPostTranslate;",
      "uniform float xAxisOrdinalSlope;",
      "uniform float xAxisOrdinalOffset;",
      "uniform float xAxisPos;",
      "uniform bool  xAxisCVSCoord;",
      "uniform bool  xAxisIsLog;",
      "uniform bool  xAxisReversed;",
      "uniform float yAxisTrans;",
      "uniform float yAxisMin;",
      "uniform float yAxisMinPad;",
      "uniform float yAxisPointRange;",
      "uniform float yAxisLen;",
      "uniform bool  yAxisPostTranslate;",
      "uniform float yAxisOrdinalSlope;",
      "uniform float yAxisOrdinalOffset;",
      "uniform float yAxisPos;",
      "uniform bool  yAxisCVSCoord;",
      "uniform bool  yAxisIsLog;",
      "uniform bool  yAxisReversed;",
      "uniform bool  isBubble;",
      "uniform bool  bubbleSizeByArea;",
      "uniform float bubbleZMin;",
      "uniform float bubbleZMax;",
      "uniform float bubbleZThreshold;",
      "uniform float bubbleMinSize;",
      "uniform float bubbleMaxSize;",
      "uniform bool  bubbleSizeAbs;",
      "uniform bool  isInverted;",
      "float bubbleRadius(){",
      "float value = aVertexPosition.w;",
      "float zMax = bubbleZMax;",
      "float zMin = bubbleZMin;",
      "float radius = 0.0;",
      "float pos = 0.0;",
      "float zRange = zMax - zMin;",
      "if (bubbleSizeAbs){",
      "value = value - bubbleZThreshold;",
      "zMax = max(zMax - bubbleZThreshold, zMin - bubbleZThreshold);",
      "zMin = 0.0;",
      "}",
      "if (value < zMin){",
      "radius = bubbleZMin / 2.0 - 1.0;",
      "} else {",
      "pos = zRange > 0.0 ? (value - zMin) / zRange : 0.5;",
      "if (bubbleSizeByArea && pos > 0.0){",
      "pos = sqrt(pos);",
      "}",
      "radius = ceil(bubbleMinSize + pos * (bubbleMaxSize - bubbleMinSize)) / 2.0;",
      "}",
      "return radius * 2.0;",
      "}",
      "float translate(float val,",
      "float pointPlacement,",
      "float localA,",
      "float localMin,",
      "float minPixelPadding,",
      "float pointRange,",
      "float len,",
      "bool  cvsCoord,",
      "bool  isLog,",
      "bool  reversed",
      "){",
      "float sign = 1.0;",
      "float cvsOffset = 0.0;",
      "if (cvsCoord) {",
      "sign *= -1.0;",
      "cvsOffset = len;",
      "}",
      "if (isLog) {",
      "val = log(val) / LN10;",
      "}",
      "if (reversed) {",
      "sign *= -1.0;",
      "cvsOffset -= sign * len;",
      "}",
      "return sign * (val - localMin) * localA + cvsOffset + ",
      "(sign * minPixelPadding);",
      "}",
      "float xToPixels(float value) {",
      "if (skipTranslation){",
      "return value;// + xAxisPos;",
      "}",
      "return translate(value, 0.0, xAxisTrans, xAxisMin, xAxisMinPad, xAxisPointRange, xAxisLen, xAxisCVSCoord, xAxisIsLog, xAxisReversed);// + xAxisPos;",
      "}",
      "float yToPixels(float value, float checkTreshold) {",
      "float v;",
      "if (skipTranslation){",
      "v = value;// + yAxisPos;",
      "} else {",
      "v = translate(value, 0.0, yAxisTrans, yAxisMin, yAxisMinPad, yAxisPointRange, yAxisLen, yAxisCVSCoord, yAxisIsLog, yAxisReversed);// + yAxisPos;",
      "if (v > yAxisLen) {",
      "v = yAxisLen;",
      "}",
      "}",
      "if (checkTreshold > 0.0 && hasThreshold) {",
      "v = min(v, translatedThreshold);",
      "}",
      "return v;",
      "}",
      "void main(void) {",
      "if (isBubble){",
      "gl_PointSize = bubbleRadius();",
      "} else {",
      "gl_PointSize = pSize;",
      "}",
      // 'gl_PointSize = 10.0;',
      "vColor = aColor;",
      "if (skipTranslation && isInverted) {",
      // If we get translated values from JS, just swap them (x, y)
      "gl_Position = uPMatrix * vec4(aVertexPosition.y + yAxisPos, aVertexPosition.x + xAxisPos, 0.0, 1.0);",
      "} else if (isInverted) {",
      // But when calculating pixel positions directly,
      // swap axes and values (x, y)
      "gl_Position = uPMatrix * vec4(yToPixels(aVertexPosition.y, aVertexPosition.z) + yAxisPos, xToPixels(aVertexPosition.x) + xAxisPos, 0.0, 1.0);",
      "} else {",
      "gl_Position = uPMatrix * vec4(xToPixels(aVertexPosition.x) + xAxisPos, yToPixels(aVertexPosition.y, aVertexPosition.z) + yAxisPos, 0.0, 1.0);",
      "}",
      // 'gl_Position = uPMatrix * vec4(aVertexPosition.x, aVertexPosition.y, 0.0, 1.0);',
      "}",
      /* eslint-enable max-len, @typescript-eslint/indent */
    ].join("\n"),
    // Fragment shader source
    fragShade = [
      /* eslint-disable max-len, @typescript-eslint/indent */
      "precision highp float;",
      "uniform vec4 fillColor;",
      "varying highp vec2 position;",
      "varying highp vec4 vColor;",
      "uniform sampler2D uSampler;",
      "uniform bool isCircle;",
      "uniform bool hasColor;",
      // 'vec4 toColor(float value, vec2 point) {',
      //     'return vec4(0.0, 0.0, 0.0, 0.0);',
      // '}',
      "void main(void) {",
      "vec4 col = fillColor;",
      "vec4 tcol;",
      "if (hasColor) {",
      "col = vColor;",
      "}",
      "if (isCircle) {",
      "tcol = texture2D(uSampler, gl_PointCoord.st);",
      "col *= tcol;",
      "if (tcol.r < 0.0) {",
      "discard;",
      "} else {",
      "gl_FragColor = col;",
      "}",
      "} else {",
      "gl_FragColor = col;",
      "}",
      "}",
      /* eslint-enable max-len, @typescript-eslint/indent */
    ].join("\n"),
    uLocations = {},
    // The shader program
    shaderProgram,
    // Uniform handle to the perspective matrix
    pUniform,
    // Uniform for point size
    psUniform,
    // Uniform for fill color
    fillColorUniform,
    // Uniform for isBubble
    isBubbleUniform,
    // Uniform for bubble abs sizing
    bubbleSizeAbsUniform,
    bubbleSizeAreaUniform,
    // Skip translation uniform
    skipTranslationUniform,
    // Set to 1 if circle
    isCircleUniform,
    // Uniform for invertion
    isInverted,
    // Error stack
    errors = [],
    // Texture uniform
    uSamplerUniform;
  /**
   * Handle errors accumulated in errors stack
   * @private
   */
  function handleErrors() {
    if (errors.length) {
      H.error("[highcharts boost] shader error - " + errors.join("\n"));
    }
  }
  /**
   * String to shader program
   * @private
   * @param {string} str - the program source
   * @param {string} type - the program type: either `vertex` or `fragment`
   * @returns {bool|shader}
   */
  function stringToProgram(str, type) {
    var t = type === "vertex" ? gl.VERTEX_SHADER : gl.FRAGMENT_SHADER,
      shader = gl.createShader(t);
    gl.shaderSource(shader, str);
    gl.compileShader(shader);
    if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
      errors.push(
        "when compiling " + type + " shader:\n" + gl.getShaderInfoLog(shader)
      );
      return false;
    }
    return shader;
  }
  /**
   * Create the shader.
   * Loads the shader program statically defined above
   * @private
   */
  function createShader() {
    var v = stringToProgram(vertShade, "vertex"),
      f = stringToProgram(fragShade, "fragment");
    if (!v || !f) {
      shaderProgram = false;
      handleErrors();
      return false;
    }
    /**
     * @private
     */
    function uloc(n) {
      return gl.getUniformLocation(shaderProgram, n);
    }
    shaderProgram = gl.createProgram();
    gl.attachShader(shaderProgram, v);
    gl.attachShader(shaderProgram, f);
    gl.linkProgram(shaderProgram);
    if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
      errors.push(gl.getProgramInfoLog(shaderProgram));
      handleErrors();
      shaderProgram = false;
      return false;
    }
    gl.useProgram(shaderProgram);
    gl.bindAttribLocation(shaderProgram, 0, "aVertexPosition");
    pUniform = uloc("uPMatrix");
    psUniform = uloc("pSize");
    fillColorUniform = uloc("fillColor");
    isBubbleUniform = uloc("isBubble");
    bubbleSizeAbsUniform = uloc("bubbleSizeAbs");
    bubbleSizeAreaUniform = uloc("bubbleSizeByArea");
    uSamplerUniform = uloc("uSampler");
    skipTranslationUniform = uloc("skipTranslation");
    isCircleUniform = uloc("isCircle");
    isInverted = uloc("isInverted");
    return true;
  }
  /**
   * Destroy the shader
   * @private
   */
  function destroy() {
    if (gl && shaderProgram) {
      gl.deleteProgram(shaderProgram);
      shaderProgram = false;
    }
  }
  /**
   * Bind the shader.
   * This makes the shader the active one until another one is bound,
   * or until 0 is bound.
   * @private
   */
  function bind() {
    if (gl && shaderProgram) {
      gl.useProgram(shaderProgram);
    }
  }
  /**
   * Set a uniform value.
   * This uses a hash map to cache uniform locations.
   * @private
   * @param name {string} - the name of the uniform to set
   * @param val {float} - the value to set
   */
  function setUniform(name, val) {
    if (gl && shaderProgram) {
      var u = (uLocations[name] =
        uLocations[name] || gl.getUniformLocation(shaderProgram, name));
      gl.uniform1f(u, val);
    }
  }
  /**
   * Set the active texture
   * @private
   * @param texture - the texture
   */
  function setTexture(texture) {
    if (gl && shaderProgram) {
      gl.uniform1i(uSamplerUniform, texture);
    }
  }
  /**
   * Set if inversion state
   * @private
   * @flag is the state
   */
  function setInverted(flag) {
    if (gl && shaderProgram) {
      gl.uniform1i(isInverted, flag);
    }
  }
  /**
   * Enable/disable circle drawing
   * @private
   */
  function setDrawAsCircle(flag) {
    if (gl && shaderProgram) {
      gl.uniform1i(isCircleUniform, flag ? 1 : 0);
    }
  }
  /**
   * Flush
   * @private
   */
  function reset() {
    if (gl && shaderProgram) {
      gl.uniform1i(isBubbleUniform, 0);
      gl.uniform1i(isCircleUniform, 0);
    }
  }
  /**
   * Set bubble uniforms
   * @private
   * @param series {Highcharts.Series} - the series to use
   */
  function setBubbleUniforms(series, zCalcMin, zCalcMax) {
    var seriesOptions = series.options,
      zMin = Number.MAX_VALUE,
      zMax = -Number.MAX_VALUE;
    if (gl && shaderProgram && series.type === "bubble") {
      zMin = pick(
        seriesOptions.zMin,
        clamp(
          zCalcMin,
          seriesOptions.displayNegative === false
            ? seriesOptions.zThreshold
            : -Number.MAX_VALUE,
          zMin
        )
      );
      zMax = pick(seriesOptions.zMax, Math.max(zMax, zCalcMax));
      gl.uniform1i(isBubbleUniform, 1);
      gl.uniform1i(isCircleUniform, 1);
      gl.uniform1i(bubbleSizeAreaUniform, series.options.sizeBy !== "width");
      gl.uniform1i(bubbleSizeAbsUniform, series.options.sizeByAbsoluteValue);
      setUniform("bubbleZMin", zMin);
      setUniform("bubbleZMax", zMax);
      setUniform("bubbleZThreshold", series.options.zThreshold);
      setUniform("bubbleMinSize", series.minPxSize);
      setUniform("bubbleMaxSize", series.maxPxSize);
    }
  }
  /**
   * Set the Color uniform.
   * @private
   * @param color {Array<float>} - an array with RGBA values
   */
  function setColor(color) {
    if (gl && shaderProgram) {
      gl.uniform4f(
        fillColorUniform,
        color[0] / 255.0,
        color[1] / 255.0,
        color[2] / 255.0,
        color[3]
      );
    }
  }
  /**
   * Set skip translation
   * @private
   */
  function setSkipTranslation(flag) {
    if (gl && shaderProgram) {
      gl.uniform1i(skipTranslationUniform, flag === true ? 1 : 0);
    }
  }
  /**
   * Set the perspective matrix
   * @private
   * @param m {Matrix4x4} - the matrix
   */
  function setPMatrix(m) {
    if (gl && shaderProgram) {
      gl.uniformMatrix4fv(pUniform, false, m);
    }
  }
  /**
   * Set the point size.
   * @private
   * @param p {float} - point size
   */
  function setPointSize(p) {
    if (gl && shaderProgram) {
      gl.uniform1f(psUniform, p);
    }
  }
  /**
   * Get the shader program handle
   * @private
   * @return {GLInt} - the handle for the program
   */
  function getProgram() {
    return shaderProgram;
  }
  if (gl) {
    if (!createShader()) {
      return false;
    }
  }
  return {
    psUniform: function () {
      return psUniform;
    },
    pUniform: function () {
      return pUniform;
    },
    fillColorUniform: function () {
      return fillColorUniform;
    },
    setBubbleUniforms: setBubbleUniforms,
    bind: bind,
    program: getProgram,
    create: createShader,
    setUniform: setUniform,
    setPMatrix: setPMatrix,
    setColor: setColor,
    setPointSize: setPointSize,
    setSkipTranslation: setSkipTranslation,
    setTexture: setTexture,
    setDrawAsCircle: setDrawAsCircle,
    reset: reset,
    setInverted: setInverted,
    destroy: destroy,
  };
}
export default GLShader;
